import {debounce as _debounce, throttle as _throttle} from 'lodash';
import {createPopper} from "@popperjs/core"
import {ElMessage} from 'element-plus'
import delegate from 'delegate'
import {isFunction, isObject} from '@/utils/types';
import {strLength} from '@/utils/validate'
import {splitStr} from '@/utils'

/**
 * @description 防抖、节流指令的修饰符
 * @param {Event} event
 * @param {Object} modifiers 修饰符对象
 * @author fengjin
 * @date 2021-07-21 17:48:19
 */
function modifiersHandle(event, modifiers) {
  if (modifiers.prevent) event.preventDefault(); // 阻止默认事件
  if (modifiers.stop) event.stopPropagation(); // 阻止冒泡
}

/**
 * @description 防抖
 * @author fengjin
 * @date 2021-07-21 16:40:35
 * @link https://www.lodashjs.com/docs/lodash.debounce
 * @example
 * <button v-debounce.prevent="submitHandle"></button>
 * <button v-debounce:change="{fn: handle, delay: 1000}"></button>
 * <button v-debounce:change="{fn: handle, delay: 1000, leading: true}"></button>
 */
const debounce = {
  mounted(el, binding) {
    const {
      event = binding.arg || 'click',
      fn = () => {
      },
      delay = 200,
      ...options
    } = isFunction(binding.value) ? {
      fn: binding.value,
    } : binding.value;

    el.addEventListener(event, _debounce(e => {
      modifiersHandle(e, binding.modifiers);
      fn();
    }, delay, options), false);
  },
};

/**
 * @description 节流
 * @author fengjin
 * @date 2021-07-21 16:45:56
 * @link https://www.lodashjs.com/docs/lodash.throttle
 * @example
 * <button v-throttle.prevent="submitHandle"></button>
 * <button v-throttle:change="{fn: handle, delay: 1000}"></button>
 * <button v-throttle:change="{fn: handle, delay: 1000, leading: true}"></button>
 */
const throttle = {
  mounted(el, binding) {
    const {
      event = binding.arg || 'click',
      fn = () => {
      },
      delay = 1000,
      ...options
    } = isFunction(binding.value) ? {
      fn: binding.value,
    } : binding.value;

    el.addEventListener(event, _throttle((e) => {
      modifiersHandle(e, binding.modifiers);
      fn();
    }, delay, options), false);
  },
};
// 单行 超出文字显示...并显示文字提示框 只需要设置元素宽即可
const showTips = {
  // el {element} 当前元素
  mounted(el, binding) {
    // 给当前元素设置超出隐藏
    el.style.overflow = 'hidden'
    el.style.textOverflow = 'ellipsis'
    el.style.whiteSpace = 'nowrap'
    // 鼠标移入
    el.onmouseenter = function (e) {
      // scrollWidth获取总宽度 包含超出  显示提示框
      if (el.scrollWidth > el.offsetWidth) {
        // 创建浮层元素并设置样式
        const div = document.createElement('div')
        const i = document.createElement('i')
        // 样式在src/styles/scss/index.scss
        div.classList.add('tooltip')
        i.classList.add('tooltip-arrow')
        // 设置id方便寻找
        div.setAttribute('id', 'tooltip')
        // 浮层中的文字
        div.innerText = el.innerText
        // 将浮层插入到body中
        div.appendChild(i)
        document.body.appendChild(div)
        // 让提示元素的位置始终保持在正确位置
        createPopper(this, div, {
          placement: 'top',
          strategy: 'fixed',
          modifiers: [
            {
              name: 'offset',
              options: {
                offset: [0, 10],
              },
            },
            {
              name: 'arrow',
              options: {
                element: i,
              },
            },
          ],
        });
      }
    }
    // 鼠标移出
    el.onmouseleave = function () {
      // 找到浮层元素并移出
      let tooltip = document.getElementById('tooltip')
      tooltip && document.body.removeChild(tooltip)
    }
  },
  // 指令与元素解绑时
  beforeUnmount(el) {
    el.onmouseenter = null
    el.onmouseleave = null
    // 找到浮层元素并移除
    // let tooltip = document.getElementById('tooltip')
    // tooltip && document.body.removeChild(tooltip)
  }
}
/*
*  v-auto-tooltip 存在省略号的时候自动出现弹出窗
*  tooltip-target-class="text_content" 需要出现提示框的目标元素类名（可不指定，不指定默认为指令所在的元素）
*  tooltip-custom-class 弹出窗自定义类名（可不指定）
* */
const autoTooltip = {
  mounted(el) {
    const div = document.createElement('div')
    const i = document.createElement('i')
    div.classList.add('tooltip')
    const customClass = el.getAttribute('tooltip-custom-class')
    if (customClass) {
      div.classList.add(customClass)
    }
    i.classList.add('tooltip-arrow')
    let popper = null
    const tooltipTarget = el.getAttribute('tooltip-target-class')
    /*if (tooltipTarget) {
      el = el.getElementsByClassName(tooltipTarget)[0]
      if (!el) {
        console.error('需要绑定的目标不存在')
      }
    }*/
    // 鼠标移入
    el.addEventListener('mouseenter', () => {
      let els = el
      if (tooltipTarget) {
        els = el.getElementsByClassName(tooltipTarget)[0]
        if (!els) {
          return false
        }
      }
      if (els.scrollHeight > els.offsetHeight || els.scrollWidth > els.offsetWidth) {
        // 浮层中的文字
        div.innerText = els.innerText
        if (popper) {
          div.style.display = 'block'
        } else {
          div.appendChild(i)
          document.body.appendChild(div)
          popper = createPopper(els, div, {
            placement: 'top',
            strategy: 'fixed',
            removeOnDestroy: true,
            modifiers: [
              {
                name: 'offset',
                options: {
                  offset: [0, 10],
                },
              },
              {
                name: 'arrow',
                options: {
                  element: i,
                },
              },
            ],
          });
          el.popper = popper
        }
      }
    })
    // 鼠标移出
    el.addEventListener('mouseleave', () => {
      // popper.destroy()
      if (popper) div.style.display = 'none'
    })
  },
  beforeUnmount(el) {
    if (el.popper) el.popper.destroy()
  }
}

let delegation = null
/**
 * @description 事件委托 精准委托
 * @author 王应龙
 * @date 2021-08-23 14:38:10
 * @link https://github.com/zenorocha/delegate
 * @returns 返回目标元素
 * @example
 * <button v-eventDelegation="{fn: handler, target: '触发目标类名'}"></button>
 * <button v-eventDelegation:change="{fn: handler, target: '触发目标类名'}"></button>
 */
const eventDelegation = {
  // el {element} 当前元素
  mounted(el, binding) {
    let event = binding.arg || 'click'
    const {
      target = false,
      fn = (e) => {
        console.log(e, '触发元素')
      },
    } = binding.value
    if (target) {
      delegation = delegate(el, target, event, function (e) {
        fn(e.delegateTarget)
      }, false);
    }
  },
  // 指令与元素解绑时
  beforeUnmount() {
    // 找到浮层元素并移除
    delegation.destroy();
  }
}

/**
 * @des 左键点击复制内容
 * @author 王应龙
 * @date 2021-08-23 14:38:10
 * @returns 点击的内容
 * @example
 * val = 要复制的内容
 * <span v-copy="val">
 *   {{ val }}
 * </span>
 */
/*let elMessage = null
const copy = {
  mounted(el, {value}) {
    el.$value = value
    el.handler = () => {
      console.log('执行了')
      if (elMessage) elMessage.close()
      if (!el.$value) {
        // 值为空的时候，给出提示。可根据项目UI仔细设计
        console.log('无复制内容')
        return
      }
      // 动态创建 textarea 标签
      const textarea = document.createElement('textarea')
      // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘，同时将 textarea 移出可视区域
      textarea.readOnly = 'readonly'
      textarea.style.position = 'absolute'
      textarea.style.left = '-9999px'
      // 将要 copy 的值赋给 textarea 标签的 value 属性
      textarea.value = el.$value
      // 将 textarea 插入到 body 中
      document.body.appendChild(textarea)
      // 选中值并复制
      textarea.select()
      const result = document.execCommand('Copy')
      if (result) {
        elMessage = ElMessage.success({
          duration: 800,
          message: '复制文字成功'
        })
      }
      document.body.removeChild(textarea)
    }
    // 绑定点击事件，就是所谓的一键 copy 啦
    el.addEventListener('click', _debounce(() => {
      el.handler()
    }, 300))
  },
  /!*!// 当传进来的值更新的时候触发
  updated(el, {value}) {
     el.$value = value
  },*!/
  // 指令与元素解绑的时候，移除事件绑定
  beforeUnmount(el) {
    el.removeEventListener('click', el.handler)
  },
}*/

/**
 * @description  元素提示说明
 * @author 王应龙
 * @date 2021-09-28
 * @returns 鼠标hover提示
 * @example
 * <span v-tooltip="提示"> 文字 </span>
 * <span v-tooltip="{txt:提示文字,delay:出现延迟时间}"> 默认文字</span>
 */
const tooltip = {
  // el {element} 当前元素
  mounted(el, binding) {
    const {
      txt = '我是提示',
      delay = 300,
    } = isObject(binding.value) ? binding.value : {txt: binding.value}
    // 鼠标移入
    let setId = null
    el.onmouseenter = function (e) {
      el.style.cursor = 'pointer'
      clearTimeout(setId)
      setId = setTimeout(() => {
        // 创建浮层元素并设置样式
        const div = document.createElement('div')
        const i = document.createElement('i')
        div.classList.add('tooltip')
        i.classList.add('tooltip-arrow')
        // 样式在src/styles/scss/index.scss
        // 设置id方便寻找
        div.setAttribute('id', 'tooltip')
        // 浮层中的文字
        div.innerHTML = txt
        div.appendChild(i)
        // 将浮层插入到body中
        document.body.appendChild(div)
        // 让提示元素的位置始终保持在正确位置
        createPopper(this, div, {
          strategy: 'fixed',
          placement: 'top',
          modifiers: [
            {
              name: 'offset',
              options: {
                offset: [0, 8],
              },
            },
            {
              name: 'arrow',
              options: {
                element: i,
              },
            },
          ],
        });
      }, delay)
    }
    // 鼠标移出
    el.onmouseleave = function () {
      el.style.cursor = 'default'
      clearTimeout(setId)
      // 找到浮层元素并移出
      let tooltip = document.getElementById('tooltip')
      tooltip && document.body.removeChild(tooltip)
    }
  },
  // 指令与元素解绑时
  beforeUnmount(el) {
    el.onmouseenter = null
    el.onmouseleave = null
    // 找到浮层元素并移除
    // const tooltip = document.getElementById('tooltip')
    // tooltip && document.body.removeChild(tooltip)
  }
}

/**
 * @description  修正elementUi输入框文本/域长度限制字数统计(中文算两个)问题
 * @date 2021-11-05
 * @example 限制字数
 * <input type="text" v-inputCount="24">
 * <input type="text" v-inputCount="{max:10,num:1,special:1}">
 */
// 更新删除图标位置
function updLocation(el, span) {
  return () => {
    let suffix = el.querySelector('.el-input__suffix')
    let input = el.querySelector('.el-input__inner')
    if (suffix) {
      suffix.classList.add('no-transition')
      suffix.style.right = span.offsetWidth + 7 + 'px'
    }
    if (input) {
      input.style.paddingRight = span.offsetWidth + 7 + (suffix?.offsetWidth ?? 0) + 'px'
    }
  }
}

/**
 * @desc  计算内容长度
 */
const inputNum = (max = 20, num = 2, special = 1) => {
  let len = 0
  for (let i = 0; i < max.length; i++) {
    const c = max.charCodeAt(i)
    // 单字节(字母数字等)
    if ((c >= 0x0001 && c <= 0x007e) || (c >= 0xff60 && c <= 0xff9f)) {
      len += special
    } else {
      // 中文
      len += num
    }
  }
  return len
};
let fn = null
const inputCount = {
  // el {element} 当前元素
  mounted(el, binding) {
    // 可输入最大字数 中文算几个字符
    const {
      max = 20,
      num = 2,
      special = 1,
    } = isObject(binding.value) ? binding.value : {max: binding.value}
    let span = document.createElement('span')
    span.classList.add('limit-count')
    let inputEl = null
    // 判断是文本域还是输入框
    if (el.querySelector('.el-input__inner')) {
      span.classList.add('limit-count-t')
      inputEl = el.querySelector('.el-input__inner')
    } else {
      span.classList.add('limit-count-b')
      inputEl = el.querySelector('.el-textarea__inner')
    }
    // 更新删除图标位置
    fn = updLocation(el, span)
    // 最大输入长度
    inputEl.setAttribute('maxlength', max)
    const handler = () => {
      span.innerText = `${inputNum(inputEl.value, num, special)}/${max}`
      let suffix = el.querySelector('.el-input__suffix')
      let input = el.querySelector('.el-input__inner')
      let clear = document.querySelector('.el-input__clear')
      if (suffix) {
        suffix.classList.add('no-transition')
        suffix.style.right = span.offsetWidth + 7 + 'px'
      }
      if (input) {
        input.style.paddingRight = span.offsetWidth + 7 + (suffix?.offsetWidth ?? 0) + 'px'
      }
      if (clear) {
        clear.style.display = 'block'
      }
    }
    const updCount = _debounce(() => {
      handler()
    }, 200, {
      leading: true,
    })
    const hideClear = () => {
      let clear = document.querySelector('.el-input__clear')
      if (clear) {
        clear.style.display = 'none'
      }
    }
    el.onmousemove = handler
    inputEl.oninput = () => {
      hideClear()
      updCount()
    }
    // 解决动态创建的input(程序化中的批量添加关键词)
    inputEl.onkeyup = updCount
    inputEl.onfocus = updCount
    inputEl.onblur = updCount
    // 输入法输入时input事件有问题 不能够实时触发
    // inputEl.addEventListener('compositionend', fn)
    // 延迟执行  字数统计
    let setId = setTimeout(() => {
      updCount()
      clearTimeout(setId)
    })
    el.appendChild(span)
  },
  // 指令与元素解绑时
  beforeUnmount(el) {
    let inputEl = null
    if (el.querySelector('.el-input__inner')) {
      inputEl = el.querySelector('.el-input__inner')
    } else {
      inputEl = el.querySelector('.el-textarea__inner')
    }
    el.onmousemove = null
    inputEl.onfocus = null
    inputEl.onkeyup = null
    inputEl.onblur = null
    inputEl.oninput = null
    // inputEl.removeEventListener('compositionend', fn)
  }
}

// 计数偏移： 使用字数限制导致输入值内容看不全
/*<el-input
v-model="account.advertiser_alias"
v-countOffset
class="input-p"
placeholder="请输入账户别名"
show-word-limit></el-input>*/
let observer = null

const countOffset = {
  // el {element} 当前元素
  mounted(el, binding) {
    // 更新输入框paddingRight
    let setId = setTimeout(() => {
      let suffix = el.querySelector('.el-input__suffix')
      let input = el.querySelector('.el-input__inner')
      // 实例化对象
      observer = new MutationObserver(_debounce((e) => {
        let max = Math.max(...[...e.map(item => item.target.offsetWidth)])
        input.style.paddingRight = max + 7 + 'px'
      }, 300))
      // 配置参数
      let config = {
        attributes: true,
        characterData: true,
        childList: true,
        subtree: true,
      }
      // 监听
      observer.observe(suffix, config)
      input.style.paddingRight = suffix.offsetWidth + 7 + 'px'
      clearTimeout(setId)
    }, 300)
  },
  // 指令与元素解绑时
  beforeUnmount(el) {
    observer && observer.disconnect()
    observer = null
  }
}

const number = { // 针对输入框只能输入整数，参数object min 最小输入值，max最大输入值
  mounted(el, binding, vnode) {
    const ele = el.tagName === 'INPUT' ? el : el.querySelector('input')
    let min = null
    let max = null
    if (binding.value) {
      min = binding.value.min
      max = binding.value.max
    }
    ele.addEventListener('keyup', function (e) {
      let val = ele.value
      val = val.replace(/[^\d]/g, '')
      ele.value = val
    })
    ele.addEventListener('change', function (e) {
      let val = ele.value
      val = Number(val.replace(/[^\d]/g, ''))
      if (min !== null && val < min) val = min
      if (max !== null && val > max) val = max
      ele.value = val
      console.log('发生改变', val)
      console.log(el)
      console.log(vnode)
      console.log(binding.instance)
      console.log(binding.instance.$refs)
      const ref_key = el.getAttribute('ref')
      console.log('ref_key', ref_key)
      binding.instance.$emit('update:modelValue', val)
    })
  }
}

/*仅适用于el-select组件滚动加载，load加载函数，interval滚动距离底部多少高度是开始加载默认为5*/
const goScroll = {
  mounted(el, binding) {
    const load = binding.value
    const interval = Number(el.getAttribute('scroll-interval')) || 5
    const popper_id = el.getElementsByClassName('select-trigger')[0].attributes['aria-describedby'].value
    const scroll_wrap = document.getElementById(popper_id).getElementsByClassName('el-select-dropdown__wrap')[0]
    const scroll_el = scroll_wrap.getElementsByClassName('el-scrollbar__view')[0]
    let scroll_wrap_height = 0
    scroll_wrap.addEventListener('scroll', () => {
      if (!scroll_wrap_height) scroll_wrap_height = scroll_wrap.offsetHeight // 滚动容器的高度（获取到后不变）
      const scroll_top = scroll_wrap.scrollTop // 滚动高度
      const scroll_el_height = scroll_el.offsetHeight // 滚动元素的高度，会随着滚动加载而变化
      /*console.log('滚动容器的高度:', scroll_wrap_height)
      console.log('滚动的高度:', scroll_top)
      console.log('滚动元素的高度', scroll_el_height)*/
      if (scroll_wrap_height + scroll_top >= scroll_el_height - interval) {
        // console.log('开始加载咯')
        load()
      }
    })
  }
}

// elementUi 对话框拖拽移动指令
/*   使用方式
 <div v-dialogDrag>
    <el-dialog></el-dialog>
</div > */
const dialogDrag = {
  mounted(el, binding, vnode, oldVnode) {
    // const dialogWrapperEl = el.querySelector('.el-dialog__wrapper');
    const dialogHeaderEl = el.querySelector('.el-dialog__header');
    const dragDom = el.querySelector('.el-dialog');
    //dialogHeaderEl.style.cursor = 'move';
    dialogHeaderEl.style.cssText += ';cursor:move;'
    dragDom.style.cssText += ';top:0px;'
    // dialogWrapperEl.style.overflow = 'hidden'

    // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
    /*   const sty = (function () {
         if (window.document.currentStyle) {
           return (dom, attr) => dom.currentStyle[attr];
         } else {
           return (dom, attr) => getComputedStyle(dom, false)[attr];
         }
       })()*/
    const sty = (dom, attr) => getComputedStyle(dom, false)[attr];

    dialogHeaderEl.onmousedown = (e) => {
      // 鼠标按下，计算当前元素距离可视区的距离
      const disX = e.clientX - dialogHeaderEl.offsetLeft;
      const disY = e.clientY - dialogHeaderEl.offsetTop;

      const screenWidth = document.body.clientWidth; // body当前宽度
      const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度，可某些环境下无法获取)

      const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
      const dragDomheight = dragDom.offsetHeight; // 对话框高度

      let minDragDomLeft = -dragDom.offsetLeft;
      let maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;
      const reservedDistance = 80;
      minDragDomLeft += -(dragDomWidth - reservedDistance);
      maxDragDomLeft += dragDomWidth - reservedDistance;

      let minDragDomTop = -dragDom.offsetTop;
      let maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;
      maxDragDomTop += dragDomheight - reservedDistance;

      // 获取到的值带px 正则匹配替换
      let styL = sty(dragDom, 'left');
      let styT = sty(dragDom, 'top');

      // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
      if (styL.includes('%')) {
        styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100);
        styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100);
      } else {
        styL = +styL.replace(/\px/g, '');
        styT = +styT.replace(/\px/g, '');
      }

      // 去掉对拖拽的响应，参考：https://blog.csdn.net/z467469274/article/details/77332830?utm_source=blogxgwz2
      let ondragstartBackup = document.ondragstart
      let ondragendBackup = document.ondragend
      document.ondragstart = function (ev) {
        ev.preventDefault();
      };
      document.ondragend = function (ev) {
        ev.preventDefault();
      };

      document.onmousemove = function (e) {
        // 通过事件委托，计算移动的距离
        let left = e.clientX - disX;
        let top = e.clientY - disY;

        // 边界处理
        if (left < minDragDomLeft) {
          left = minDragDomLeft;
        } else if (left > maxDragDomLeft) {
          left = maxDragDomLeft;
        }

        if (top < minDragDomTop) {
          top = minDragDomTop;
        } else if (top > maxDragDomTop) {
          top = maxDragDomTop;
        }

        // 移动当前元素
        dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`;
        e.preventDefault()
      };

      document.onmouseup = function (e) {
        document.onmousemove = null;
        document.onmouseup = null;
        document.ondragstart = ondragstartBackup
        document.ondragend = ondragendBackup
      };
    }
  },
}

export default {
  debounce,
  throttle,
  showTips,
  eventDelegation,
  tooltip,
  number,
  inputCount,
  goScroll,
  autoTooltip,
  countOffset,
  dialogDrag
  // copy
};
